/*********************************************************************
* File: Senses.cpp
*
* \brief Implements the vision API.
*
* \author Instituto Superior Tecnico de Lisboa, Portugal
* \author Technical University of Lisbon, Portugal
* 
* \author Agentes Autonomos e Sistemas Multiagente
* \author Autonomous Agents and Multiagent Systems
* 
* \version	1.0
* \date		2006/2007
*********************************************************************/
#include "Senses.h"
#include "../../util/mathLibrary.h"
#include "../../util/util.h"
#include "../Agent.h"
using namespace agents;

/**
* \brief The class constructor.
*
* \param[in] The agent
**/
Senses::Senses(Agent &owner)
:_owner(owner)
{
	_visibleTeammates = new IVision::IVisibleAgent*[gpGlobals->maxClients];
	_visibleEnemies = new IVision::IVisibleAgent*[gpGlobals->maxClients];
	for(int i=0; i<gpGlobals->maxClients; i++)
	{
		_visibleTeammates[i] = new VisibleAgent();
		_visibleEnemies[i] = new VisibleAgent();
	}
	_visibleRadarUnits = new IRadar::RadarUnit[gpGlobals->maxClients+1];
}

/**
* \brief The class destructor.
**/
Senses::~Senses()
{
	for(int i=0; i<gpGlobals->maxClients; i++)
	{
		delete _visibleTeammates[i];
		delete _visibleEnemies[i];
	}
	delete _visibleTeammates;
	delete _visibleEnemies;
	delete _visibleRadarUnits;
}

/**
* \brief Setups for the new round. 
* 
* Should be invoked when a new round starts.
**/
void Senses::newRound(){
}


/**
* \brief Gets the agent's field-of-view. 
*
* Defaults to 90 degrees. 
*
* \see agents::interfaces::IVision::getFieldOfView
**/
float Senses::getFieldOfView()
{
	return _owner.getEdict()->v.fov;
}
/**
* \brief Gets visible teammates.
* 
* \param[out] count The number of visible units.
* \return Array of visible units.
*
* \see agents::interfaces::IVision::seeTeammates
**/
IVision::IVisibleAgent** Senses::seeTeammates(int* count)
{
	updateSeenBots();
	*count = _teammatesCount;
	return _visibleTeammates;
}

/**
* \brief Gets visible enemies.
* 
* \param[out] count The number of visible units.
* \return Array of visible units.
*
* \see agents::interfaces::IVision::seeEnemies
**/
IVision::IVisibleAgent** Senses::seeEnemies(int* count)
{
	updateSeenBots();
	*count = _enemiesCount;
	return _visibleEnemies;
}
/**
* \brief Auxiliar function to update the visible agents information.
**/
void Senses::updateSeenBots()
{
	CounterStrikeTeams::Enumeration myTeam = _owner.getTeam();	
	_teammatesCount = _enemiesCount = 0;
	for (int i = 0; i < gpGlobals->maxClients; i++) 
	{
		if((g_threatTab[i].pEdict != _owner.getEdict())
			&& util::isAlive(g_threatTab[i].pEdict) 
			&& agentIsVisible(g_threatTab[i].pEdict))
		{
			if( util::getTeam(g_threatTab[i].pEdict) == myTeam)
				dynamic_cast<VisibleAgent*>(_visibleTeammates[_teammatesCount++])->setAgent(g_threatTab[i].pEdict, true);
			else 
				dynamic_cast<VisibleAgent*>(_visibleEnemies[_enemiesCount++])->setAgent(g_threatTab[i].pEdict, false);
		}
	}
}
/**
* \brief Gets radar visible units.
* 
* \param[out] count The number of visible units.
* \return Array of visible units.
*
* \see agents::interfaces::IRadar::getRadarVisibleUnits
**/
IRadar::RadarUnit* Senses::getRadarVisibleUnits(int* count)
{	
	memset(_visibleRadarUnits, 0, gpGlobals->maxClients * sizeof(IRadar::RadarUnit*)); 

	CounterStrikeTeams::Enumeration myTeam = _owner.getTeam();	
	int unitsCount = 0;
	Vector toUnit;

	// (1) Handle teammates
	bool bomberExists = false;
	for (int i = 0; i < gpGlobals->maxClients; i++) 
	{
		if((g_threatTab[i].pEdict != _owner.getEdict())
			&& util::isAlive(g_threatTab[i].pEdict))
		{
			if( util::getTeam(g_threatTab[i].pEdict) == myTeam )
			{
				bool bomberExists = false;
				if(myTeam == CounterStrikeTeams::terrorist)
					bomberExists = (g_threatTab[i].pEdict->v.weapons & (1 << WeaponIds::c4)) != 0;
				_visibleRadarUnits[unitsCount].set(_owner.getNavigation().getPosition(),
					g_threatTab[i].pEdict->v.origin, true, bomberExists, false);
				unitsCount++;
			}
		}
	}

	// (2) Handle the bomb
	if(!bomberExists && (myTeam == CounterStrikeTeams::terrorist))
	{
		bool isArmedBomb = g_world->isBombPlanted();
		_visibleRadarUnits[unitsCount].set(_owner.getNavigation().getPosition(), 
			g_world->getBombPosition(isArmedBomb), false, true, isArmedBomb);
		unitsCount++;
	}

	*count = unitsCount;
	return _visibleRadarUnits;
}
/**
* \brief Sets the radar unit.
*
* \param[in] ownerPosition The radar owner position.
* \param[in] unitPosition The unit position.
* \param[in] isTeammate Whether unit is a teammate.
* \param[in] isBomb Whether unit is a bomb.
* \param[in] isArmedBomb Whether unit is an armed bomb.
**/
void IRadar::RadarUnit::set(Vector ownerPosition, Vector unitPosition, bool isTeammate, bool isBomb, bool isArmedBomb)
{
	Vector toUnit = unitPosition - ownerPosition;
	this->direction = toUnit.Normalize();
	this->distance = min(2200, toUnit.Length());
	this->isTeammate = isTeammate;
	this->isBomb = isBomb;
	this->isArmedBomb = isArmedBomb;
}
/**
* \brief Whether another agent is visible.
*
* A agent is considered visible if one of the following body parts 
* is visible: head, pelvis, left forearm or right forearm.
*
* \param[in] ent The other agent.
* \return If agent is visible, 'true'; otherwise, 'false'.
**/
bool Senses::agentIsVisible(edict_t* ent)
{
    return(agentBodyPartIsVisible(ent, BodyPart::head) ||
		agentBodyPartIsVisible(ent, BodyPart::pelvis) ||
        agentBodyPartIsVisible(ent,  BodyPart::l_forearm) ||
        agentBodyPartIsVisible(ent,  BodyPart::r_forearm));
}
/**
* \brief Whether a body part from another agent is visible.
*
* \param[in] agent The other agent.
* \param[in] bodyPart The bo part.
* \param[out] bodyPartPosition The body part position. Is only set if body part is visible.
* \return If body part is visible, 'true'; otherwise, 'false'.
**/
bool Senses::agentBodyPartIsVisible(edict_t* ent, BodyPart::Enumeration bodyPart, Vector* bodyPartPosition)
{
	Vector position;
	util::getBonePosition(ent, bodyPart, &position);

	if(isPointVisible(position))
	{
		if(bodyPartPosition)
			*bodyPartPosition = position;
		return true;
	}
	return false;
}
/**
* \bried Whether a point in the world is visible.
*
* \param[in] point The point.
* \return If point is visible, 'true'; otherwise, 'false'.
*
* \see agents::interfaces::IVision::isPointVisible
**/
bool Senses::isPointVisible(Vector point)
{
	if(!inViewCone(point))
		return false;

	TraceResult tr;
	Vector origin;
	edict_t* pEdict = _owner.getEdict();

	origin = pEdict->v.origin + pEdict->v.view_ofs; // Look through caller's eyes

	bool bInWater = (POINT_CONTENTS(point) == CONTENTS_WATER);
	bool bLookerInWater = (POINT_CONTENTS(origin) == CONTENTS_WATER);

	if (bInWater != bLookerInWater) // Don't look through water
		return false;

	util::traceline(origin, point, ignore_monsters, ignore_glass, pEdict, &tr);

	if (tr.flFraction != 1.0)
		return false;  // Line of sight is not established
	else
		return true;  // Line of sight is valid.
}
/**
* \brief Whether a point is within the field-of-view.
*
* \param[in] point The point.
**/
bool Senses::inViewCone(Vector point)
{
	edict_t* pEdict = _owner.getEdict();

	Vector direction = (point - (pEdict->v.origin + pEdict->v.view_ofs)).Normalize();
	util::math::makeVectors(pEdict->v.v_angle);
	float cosShootingDeviation (DotProduct(gpGlobals->v_forward, direction));

	return cosShootingDeviation >= cos((getFieldOfView() / 2) * M_PI / 180);
}
/**
* \brief Gets visible weapons on the floor.
* 
* \param[in] weapons The array of weapons to be filled.
* \param[in,out] count Pass the maximum number of weapons to perceive as argument. 
* Is set to actual number of weapons found.
*
* \see agents::interfaces::IVision::seeWeaponsOnTheFloor
**/
void Senses::seeWeaponsOnTheFloor(PickableWeapon* weapons, int* count)
{
	int index = 0;
	edict_t *pent = NULL;
	Vector position = _owner.getNavigation().getPosition();
	while((index < *count) && (!FNullEnt(pent = FIND_ENTITY_IN_SPHERE( pent, position, 500 ))))
	{
		if (pent->v.effects & EF_NODRAW)
			continue; // someone owns this weapon or it hasn't respawned yet

		if(!_owner.getVision().isPointVisible(pent->v.origin))
			continue;

		if (strcmp("weaponbox", STRING(pent->v.classname)) == 0)
		{
			weapons[index].position = pent->v.origin;
			weapons[index].position.z = position.z; // So as to define a proper position height to move into
			weapons[index].weapon = _owner.combat->weaponModelNameToId(STRING(pent->v.model) + 9);
			index++;
		}
	}

	*count = index;
}
/**
* \brief Whether the armed bomb is visible.
*
* \param[out] position The bomb position.
*
* \see agents::interfaces::IVision::isArmedBombVisible
**/
bool Senses::isArmedBombVisible(Vector* position)
{
	Vector bombPosition = Vector(9999, 9999, 9999);
	edict_t *pent = NULL;

	while (!FNullEnt(pent = FIND_ENTITY_BY_STRING( pent, "classname", "grenade" )))
	{
		if (FStrEq(STRING(pent->v.model) + 9, "c4.mdl"))
		{
			bombPosition = pent->v.origin;
			break;
		}
	}

	bombPosition.z = _owner.getNavigation().getPosition().z;
	if(isPointVisible(bombPosition))
	{
		*position = bombPosition;
		return true;
	}
	else 
		return false;
}
/**
* \brief Whether the dropped bomb is visible.
*
* \param[out] position The bomb position.
*
* \see agents::interfaces::IVision::isDroppedBombVisible
**/
bool Senses::isDroppedBombVisible(Vector* position)
{
	Vector bombPosition = Vector(9999, 9999, 9999);
	edict_t *pent = NULL;

	while (!FNullEnt(pent = FIND_ENTITY_BY_STRING( pent, "classname", "weaponbox" )))
	{
		if (FStrEq(STRING(pent->v.model) + 9, "backpack.mdl"))
		{
			bombPosition = pent->v.origin;
			break;
		}
	}

	if(isPointVisible(bombPosition))
	{
		*position = bombPosition;
		return true;
	}
	else 
		return false;
}
/**
* \brief Whether the bomber is visible.
*
* \param[out] bomber The agent.
*
* \see agents::interfaces::IVision::isBomberVisible
**/
bool Senses::isBomberVisible(IVisibleAgent* bomber)
{
	for (int i = 0; i < gpGlobals->maxClients; i++) 
	{
		if((g_threatTab[i].pEdict != _owner.getEdict())
			&& util::isAlive(g_threatTab[i].pEdict) 
			&& agentIsVisible(g_threatTab[i].pEdict))
		{
			if((g_threatTab[i].pEdict->v.weapons & (1 << WeaponIds::c4)) != 0)
			{
				dynamic_cast<VisibleAgent*>(bomber)->setAgent(g_threatTab[i].pEdict, _owner.getTeam() == CounterStrikeTeams::terrorist);
				return true;
			}
		}
	}

	return false;
}
/**
* \brief Whether an agent damage this agent.
*
* \param[in] shooter The possible shooter.
*
* \see agents::interfaces::IVision::inflictedDamage
**/
bool Senses::inflictedDamage(IVisibleAgent* shooter)
{
	return _owner.getEdict()->v.dmg_inflictor == dynamic_cast<VisibleAgent*>(shooter)->getAgent();
}
/**
* \brief Whether a HE grenade is visible.
*
* \param[out] position The grenade position.
*
* \see agents::interfaces::IVision::isHeGrenadeVisible
**/
bool Senses::isHeGrenadeVisible(Vector* position)
{
	return isGrenadeVisible(HeGrenadeModelName, position);
}
/**
* \brief Whether a smoke grenade is visible.
*
* \param[out] position The grenade position.
*
* \see agents::interfaces::IVision::isSmokeGrenadeVisible
**/
bool Senses::isSmokeGrenadeVisible(Vector* position)
{
	return isGrenadeVisible(SmokeGrenadeModelName, position);
}
/**
* \brief Whether a flashbang is visible.
*
* \param[out] position The flashbang position.
*
* \see agents::interfaces::IVision::isFlashbangVisible
**/
bool Senses::isFlashbangVisible(Vector* position)
{
	return isGrenadeVisible(FlashbangModelName, position);
}
/**
* \brief Whether a grenade is visible
*
* \param[in] grenadeModelName The respective model name.
* \param[out] grenadePosition If grenade is visible then it is set to the grenade position.
* \return If the agent sees a grenade, 'true'; otherwise, 'false'
**/
bool Senses::isGrenadeVisible(const char* grenadeModelName, Vector* position)
{
	edict_t* grenadeEntity = NULL;

	while (!FNullEnt(grenadeEntity = FIND_ENTITY_BY_CLASSNAME(grenadeEntity, "grenade")))
	{
		if(isPointVisible(grenadeEntity->v.origin) &&
			(FStrEq(STRING(grenadeEntity->v.model) + 9, grenadeModelName)))
		{
			if(position)
				*position = grenadeEntity->v.origin;
			return true;
		}
	}
	return false;
}

/**
* \brief Return the absolute value of angle to destination entity.
*
* Zero degrees means straight ahead, 45 degrees to the left or
* 45 degrees to the right is the limit of the normal view angle
*
* \param[in] target The target.
**/
int Senses::getAngleInFieldOfView(Vector target)
{
	// find yaw angle from source to destination...
	float entity_angle = util::math::angleMod(util::math::vectorToYaw(target));

	// get agent's current view angle...
	float view_angle = util::math::angleMod(_owner.pev->v_angle.y);

	// rsm - START angle bug fix
	int angle = abs((int)view_angle - (int)entity_angle);
	if (angle > 180)
		angle = 360 - angle;

	return angle;
	// rsm - END
}
/**
* \brief Whether the agent can hear the bomb.
*
* Hearing distance varies according to elapsed time since bomb was 
* planted as follows:
* - if elapsed time in ]0, 15[ secs, hears if distance is in [0, 650[
* - if elapsed time in [15, 22.5[ secs, hears if distance is in [0, 1000[
* - if elapsed time in [22.5, 40[ secs, hears if distance is in [0, 2000[
* - if elapsed time in > 40 secs, always hears
* If can hear the bomb, than the direction of the sound is also provided.
*
* \param[out] direction The sound direction.
*
* \see agents::interfaces::IHearing::hearsBomb
**/
bool Senses::hearsBomb(Vector* direction)
{
	if(!g_world->isBombPlanted())
		return false;

	float elapsedTime = gpGlobals->time - g_world->getTimeBombWasPlanted();
	Vector directionAux = g_world->getBombPosition() - _owner.getNavigation().getPosition();
	float distance = directionAux.Length();
	
	// BUG 04: Incorrect hearing distance for the time range [30, 40]: Start
	// Simulates game conditions for hearing the bomb ticking
	if((distance < 650.0)
		|| ((elapsedTime >= 15.0 && elapsedTime < 22.5) && (distance < 1000.0))
		|| ((elapsedTime >= 22.5 && elapsedTime < 40.0) && (distance < 2000.0))
		|| (elapsedTime >= 40.0))
	// BUG 04: End
	{
		*direction = (directionAux / distance);
		return true;
	}

	return false;
}
/**
* \brief Sets the agent.
*
* \param[in] agent The agent.
* \param[in] isSameTeam Whether the visible agent is a teammate.
**/
void VisibleAgent::setAgent(edict_t* agent, bool isSameTeam)
{
	_canSeeHealth = isSameTeam;
	_entity = agent;
	util::convertNameToHuman(STRING(_entity->v.netname), _name);
}
/**
* \brief Gets the agent's name.
*
* \see agents::interfaces::IVision::IVisibleAgent::getName
**/
const char* VisibleAgent::getName()
{
	return _name;
}
/**
* \brief Gets the agent's position.
*
* \see agents::interfaces::IVision::IVisibleAgent::getPosition
**/
Vector VisibleAgent::getPosition()
{
	return _entity->v.origin;
}
/**
* \brief Gets the agent's body part position.
*
* \param[in] bodyPart The body part. 
*
* \see agents::interfaces::IVision::IVisibleAgent::
**/
Vector VisibleAgent::getBodyPartPosition(BodyPart::Enumeration bodyPart)
{
	Vector position;
	util::getBonePosition(_entity, bodyPart, &position);
	return position;
}

/**
* \brief Gets the agent's team.
*
* \see agents::interfaces::IVision::IVisibleAgent::getTeam
**/
CounterStrikeTeams::Enumeration VisibleAgent::getTeam()
{
	return util::getTeam(_entity);
}
/**
* \brief Gets the agent's primary weapon.
*
* \see agents::interfaces::IVision::IVisibleAgent::getPrimaryWeapon
**/
WeaponIds::Enumeration VisibleAgent::getPrimaryWeapon()
{
	int weapons = _entity->v.weapons;

	if(weapons & (1<<WeaponIds::m3))
		return WeaponIds::m3;
	else if(weapons & (1<<WeaponIds::xm1014))
		return WeaponIds::xm1014;
	else if(weapons & (1<<WeaponIds::mac10))
		return WeaponIds::mac10;
	else if(weapons & (1<<WeaponIds::tmp))
		return WeaponIds::tmp;
	else if(weapons & (1<<WeaponIds::mp5Navy))
		return WeaponIds::mp5Navy;
	else if(weapons & (1<<WeaponIds::ump45))
		return WeaponIds::ump45;
	else if(weapons & (1<<WeaponIds::p90))
		return WeaponIds::p90;
	else if(weapons & (1<<WeaponIds::famas))
		return WeaponIds::famas;
	else if(weapons & (1<<WeaponIds::galil))
		return WeaponIds::galil;
	else if(weapons & (1<<WeaponIds::ak47))
		return WeaponIds::ak47;
	else if(weapons & (1<<WeaponIds::scout))
		return WeaponIds::scout;
	else if(weapons & (1<<WeaponIds::m4A1))
		return WeaponIds::m4A1;
	else if(weapons & (1<<WeaponIds::aug))
		return WeaponIds::aug;
	else if(weapons & (1<<WeaponIds::sg550))
		return WeaponIds::sg550;
	else if(weapons & (1<<WeaponIds::sg552))
		return WeaponIds::sg552;
	else if(weapons & (1<<WeaponIds::awp))
		return WeaponIds::awp;
	else if(weapons & (1<<WeaponIds::g3Sg1))
		return WeaponIds::g3Sg1;
	else if(weapons & (1<<WeaponIds::m249))
		return WeaponIds::m249;
	else
		return WeaponIds::null;
}
/**
* \brief Gets the agent's secondary weapon.
*
* \see agents::interfaces::IVision::IVisibleAgent::getSecondaryWeapon
**/
WeaponIds::Enumeration VisibleAgent::getSecondaryWeapon()
{
	int weapons = _entity->v.weapons;

	if(weapons & (1<<WeaponIds::glock18))
		return WeaponIds::glock18;
	else if(weapons & (1<<WeaponIds::usp))
		return WeaponIds::usp;
	else if(weapons & (1<<WeaponIds::p228))
		return WeaponIds::p228;
	else if(weapons & (1<<WeaponIds::dEagle))
		return WeaponIds::dEagle;
	else if(weapons & (1<<WeaponIds::elite))
		return WeaponIds::elite;
	else if(weapons & (1<<WeaponIds::fiveSeven))
		return WeaponIds::fiveSeven;
	else
		return WeaponIds::null;
}
/**
* \brief Whether the agent is a human player.
*
* \see agents::interfaces::IVision::IVisibleAgent::isHumanPlayer
**/
bool VisibleAgent::isHumanPlayer()
{
	return util::isHumanPlayer(_entity);
}
/**
* \brief Gets the agent's health.
* 
* Agents can only see health of teammates.
*
* \see agents::interfaces::IVision::IVisibleAgent::getHealth
**/
int VisibleAgent::getHealth()
{
	if(_canSeeHealth)
		return _entity->v.health;
	else
		return -1;
}